This study allows us to revisit/renew

  1. Regression modeling
  2. Properties of Least Squares/Fitting “a line”
  3. Multiple observation

Datasets for this study are

  1. The main file: gauge.txt
  2. Supplementary large-scale files: download the following folder Full Resolution Data.zip More information about the supplementary file can be found at http://iabp.apl.washington.edu/data.html as well as http://nsidc.org/data/G00791

Question

The aim of this lab is to provide a simple procedure for converting gain into density when the gauge is in operation. Keep in mind that the experiment was conducted by varying density and measuring the response in gain, but when the gauge is ultimately in use, the snow-pack density is to be estimated from the measured gain.

Setup

df <- read.table('gauge-1wb1wa6-2gpel41.txt', header=TRUE)
df <- df[order(df$density), ]  # Sort from least to greatest density
m <- 9  # Number of distinct block densities
t <- 10  # Number of replicate measurements
#install.packages('L1pack')
#install.packages('quantreg')
#install.packages('ggplot2')
library(L1pack)  # Used for least absolute deviations regression line
library(quantreg)  # Used for quantile regression line
library(ggplot2)

Scenario 1: Fitting

Use the data to fit the gain, or a transformation of gain, to density. Try sketching the least squares line on a scatter plot.

# Plot raw data
title <- 'Density vs. Gain'
x.axis <- expression('Density (g/cm'^3*')')
y.axis <- 'Gain'
x.range <- c(0, .7)
y.range <- c(2.5, 6)
plot(df, main=title, xlab=x.axis, ylab=y.axis, xlim=x.range)

# Take log transformation of response variable (gain)
y.log.axis = 'log(Gain)'
df.log = data.frame(df['density'], log(df['gain']))
plot(df.log, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)

# Average replicate measurements
df.log.avg = aggregate(list(gain=df.log$gain), by=list(density=df.log$density), FUN=mean)
plot(df.log.avg, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)

# Fit gain to density
least.squares <- lm(gain~density, data=df.log.avg)
lad <- lad(gain~density, data=df.log.avg)
quant <- rq(gain~density, tau=.5, data=df.log.avg)
plot(df.log, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)
abline(least.squares, col='red')
abline(lad, col='blue')
abline(quant, col='green')
legend('topright', legend=c('Least Squares Regression Line', 'Least Absolute Deviations Regression Line', '50% Quantile Regression Line'), col=c('red', 'blue', 'green'), lty=1)

c(cor(df.log.avg), summary(least.squares)$r.squared)
[1]  1.0000000 -0.9984469 -0.9984469  1.0000000  0.9968963
least.squares

Call:
lm(formula = gain ~ density, data = df.log.avg)

Coefficients:
(Intercept)      density  
      5.997       -4.606  
lad
Call:
lad(formula = gain ~ density, data = df.log.avg)
Converged in 4 iterations

Coefficients:
 (Intercept)     density 
     5.9850     -4.5935 

Degrees of freedom: 9 total; 7 residual
Scale estimate: 0.06926379 
quant
Call:
rq(formula = gain ~ density, tau = 0.5, data = df.log.avg)

Coefficients:
(Intercept)     density 
   5.985029   -4.593460 

Degrees of freedom: 9 total; 7 residual
# Check conditions for linear regression: linearity, normality of residuals, and constant variability
least.squares.residuals <- data.frame(df.log['density'], df.log['gain'] - rep(predict(least.squares), each=10))
lad.residuals <- data.frame(df.log['density'], df.log['gain'] - rep(predict(lad), each=10))
quant.residuals <- data.frame(df.log['density'], df.log['gain'] - rep(predict(quant), each=10))
title.residuals1 <- 'Residuals of Least Squares Regression Line'
title.residuals2 <- 'Residuals of Least Absolute Deviations Regression Line'
title.residuals3 <- 'Residuals of 50% Quantile Regression Line'
plot(least.squares.residuals$gain, main=title.residuals1, ylab=y.axis)
abline(0, 0, col='red')

plot(lad.residuals$gain, main=title.residuals2, ylab=y.axis)
abline(0, 0, col='blue')

plot(quant.residuals$gain, main=title.residuals3, ylab=y.axis)
abline(0, 0, col='green')

num.bins <- 12
hist(least.squares.residuals$gain, breaks=num.bins, main=title.residuals1, xlab=y.axis, col='red')

hist(lad.residuals$gain, breaks=num.bins, main=title.residuals2, xlab=y.axis, col='blue')

hist(quant.residuals$gain, breaks=num.bins, main=title.residuals3, xlab=y.axis, col='green')

qqnorm(least.squares.residuals$gain, main=paste('Normal Q-Q Plot with', title.residuals1), cex.main=1)
qqline(least.squares.residuals$gain, col='red')

qqnorm(lad.residuals$gain, main=paste('Normal Q-Q Plot with', title.residuals2), cex.main=1)
qqline(lad.residuals$gain, col='blue')

qqnorm(quant.residuals$gain, main=paste('Normal Q-Q Plot with', title.residuals3), cex.main=1)
qqline(quant.residuals$gain, col='green')

Scenario 2: Predicting

Ultimately we are interested in answering questions such as: Given a gain reading of 38.6, what is the density of the snow-pack? Given a gain reading of 426.7, what is the density of the snow-pack? These two numeric values, 38.6 and 426.7, were chosen because they are the average gains for the 0.508 and 0.001 densities, respectively.

# Predictions
PredictLogGain <- function(density)
  predict(least.squares, data.frame(density=density))  # Predict log(gain) using density
PredictDensityLeastSquares <- function(gain) {
  intercept <- coef(least.squares)[[1]]
  slope <- coef(least.squares)[[2]]
  (log(gain) - intercept) / slope  # Predict density using gain
}
PredictDensityLad <- function(gain) {
  intercept <- coef(lad)[[1]]
  slope <- coef(lad)[[2]]
  (log(gain) - intercept) / slope  # Predict density using gain
}
PredictDensityQuant <- function(gain) {
  intercept <- coef(quant)[[1]]
  slope <- coef(quant)[[2]]
  (log(gain) - intercept) / slope  # Predict density using gain
}
# 95% prediction and confidence intervals of log(gain) using density
t <- qt(.975, df=m-2)
mean.density <- mean(df.log.avg$density)
summation <- sum((df.log.avg$density - mean.density) ^ 2)
s2 <- aggregate(list(variance=least.squares.residuals$gain), by=list(density=least.squares.residuals$density), FUN=var)
s.pooled <- sqrt(mean(s2$variance))
center.expr <- quote(center <- PredictLogGain(density))
ci.width.expr <- quote(width <- t * s.pooled * sqrt(1/m + (density-mean.density)^2 / summation))
pi.width.expr <- quote(width <- t * s.pooled * sqrt(1 + 1/m + (density-mean.density)^2 / summation))
LogGainCiLower <- function(density) {
  eval(center.expr)
  eval(ci.width.expr)
  center - width
}
LogGainCiUpper <- function(density) {
  eval(center.expr)
  eval(ci.width.expr)
  center + width
}
LogGainPiLower <- function(density) {
  eval(center.expr)
  eval(pi.width.expr)
  center - width
}
LogGainPiUpper <- function(density) {
  eval(center.expr)
  eval(pi.width.expr)
  center + width
}
# Add bands around least squares line
plot(df.log, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)
abline(least.squares, col='red')
ci.col <- 'purple'
pi.col <- 'blue'
symbol <- '-'
size <- 1.5
line.type <- 3
line.width <- 0.7
confidence.intervals <- data.frame(density=df.log.avg$density, lower=LogGainCiLower(df.log.avg$density), upper=LogGainCiUpper(df.log.avg$density))
points(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, pch=symbol, cex=size)
points(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, pch=symbol, cex=size)
lines(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, lty=line.type, lwd=line.width)
lines(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, lty=line.type, lwd=line.width)
prediction.intervals <- data.frame(density=df.log.avg$density, lower=LogGainPiLower(df.log.avg$density), upper=LogGainPiUpper(df.log.avg$density))
points(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, pch=symbol, cex=size)
points(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, pch=symbol, cex=size)
lines(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, lty=line.type, lwd=line.width)
lines(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, lty=line.type, lwd=line.width)
legend('topright', legend=c('Least Squares Regression Line', '95% Confidence Interval Bands', '95% Prediction Interval Bands'), col=c('red', ci.col, pi.col), lty=1)

# 95% prediction and confidence intervals of density using gain
end.points <- c(-1, 3)  # Interval to search the root in
DensityCi <- function(gain) {
  lower <- uniroot(function(density) log(gain) - LogGainCiLower(density), interval=end.points)[[1]]
  upper <- uniroot(function(density) log(gain) - LogGainCiUpper(density), interval=end.points)[[1]]
  c(lower, upper)
}
DensityPi <- function(gain) {
  lower <- uniroot(function(density) log(gain) - LogGainPiLower(density), end.points)[[1]]
  upper <- uniroot(function(density) log(gain) - LogGainPiUpper(density), end.points)[[1]]
  c(lower, upper)
}
# Point and interval estimates for example gain readings
PredictDensityLeastSquares(38.6)  # 38.6 is the average gain for 0.508 density
[1] 0.5089113
PredictDensityLad(38.6)
[1] 0.5076298
PredictDensityQuant(38.6)
[1] 0.5076298
DensityCi(38.6)
[1] 0.5011879 0.5169000
DensityPi(38.6)
[1] 0.4889568 0.5291323
PredictDensityLeastSquares(426.7)  # 426.7 is the average gain for 0.001 density
[1] -0.01276954
PredictDensityLad(426.7)
[1] -0.01546807
PredictDensityQuant(426.7)
[1] -0.01546811
DensityCi(426.7)
[1] -0.024285866 -0.001769193
DensityPi(426.7)
[1] -0.034644666  0.008618629

Scenario 3: Cross-Validation

To check how well your procedure works, omit the set of measurements corresponding to the block of density 0.508, apply your “estimation”/calibration procedure to the remaining data, and provide an interval estimate for the density of a block with an average reading of 38.6. Where does the actual density fall in the interval? Try the same test, for the set of measurements at the 0.001 density.

for (omitted in c(0.508, 0.001)) {
  # Omit measurements corresponding to the specified density
  df.log.omitted = df.log[which(df.log['density'] != omitted), ]
  df.log.avg.omitted <- df.log.avg[which(df.log.avg['density'] != omitted), ]
  
  
  # Redo calculations using modified dataset
  least.squares <- lm(gain~density, data=df.log.avg.omitted)
  
  mean.density <- mean(df.log.avg.omitted$density)
  summation <- sum((df.log.avg.omitted$density - mean.density) ^ 2)
  
  s2 <- aggregate(list(variance=least.squares.residuals$gain), by=list(density=least.squares.residuals$density), FUN=var)
  s.pooled <- sqrt(mean(s2$variance))
  
  ci.width.expr <- quote(width <- t * s.pooled * sqrt(1/(m-1) + (density-mean.density)^2 / summation))
  pi.width.expr <- quote(width <- t * s.pooled * sqrt(1 + 1/(m-1) + (density-mean.density)^2 / summation))
  
  plot(df.log.omitted, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)
  abline(least.squares, col='red')
  
  ci.col <- 'purple'
  pi.col <- 'blue'
  symbol <- '-'
  size <- 1.5
  line.type <- 3
  line.width <- 0.7
  
  confidence.intervals <- data.frame(density=df.log.avg.omitted$density, lower=LogGainCiLower(df.log.avg.omitted$density), upper=LogGainCiUpper(df.log.avg.omitted$density))
  points(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, pch=symbol, cex=size)
  points(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, pch=symbol, cex=size)
  lines(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, lty=line.type, lwd=line.width)
  lines(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, lty=line.type, lwd=line.width)
  
  prediction.intervals <- data.frame(density=df.log.avg.omitted$density, lower=LogGainPiLower(df.log.avg.omitted$density), upper=LogGainPiUpper(df.log.avg.omitted$density))
  points(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, pch=symbol, cex=size)
  points(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, pch=symbol, cex=size)
  lines(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, lty=line.type, lwd=line.width)
  lines(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, lty=line.type, lwd=line.width)
  
  legend('topright', legend=c('Least Squares Regression Line', '95% Confidence Interval Bands', '95% Prediction Interval Bands'), col=c('red', ci.col, pi.col), lty=1)
  
  print(PredictDensityLeastSquares(38.6))  # 38.6 is the average gain for 0.508 density
  print(DensityCi(38.6))
  print(DensityPi(38.6))
  
  print(PredictDensityLeastSquares(426.7))  # 426.7 is the average gain for 0.001 density
  print(DensityCi(426.7))
  print(DensityPi(426.7))
}
[1] 0.5091927
[1] 0.5006695 0.5180406
[1] 0.4889184 0.5297925
[1] -0.0128045
[1] -0.024342164 -0.001790802
[1] -0.034760736  0.008598777

[1] 0.5092919
[1] 0.5014370 0.5174323
[1] 0.4890257 0.5298473
[1] -0.02051733
[1] -0.035344991 -0.006521825
[1] -0.044604850  0.002738127

Additional Scenario: Temperature, DOY, and Latitude.

Use the additional dataset to construct a model fitting temperature with DOY, latitude, and other reasonable features. Try sketching the least squares line on a scatter plot. We aim to investigate the relationship between temperature and the DOY, and its latitude.

# Check the correlation
data <- read.csv('Full Resolution Data/64506420.csv', header=TRUE)
data <- data[,c('Hour','DOY','POS_DOY','Lat','Lon','Ts','BP')]
# Drop the extreme outlier case
#data <- data[which(data$Ts>-200),]
data_matrix <- as.matrix(data)
# Correlation Matrix
corr_matrix <- cor(data_matrix)
corr_matrix
                Hour          DOY      POS_DOY          Lat         Lon           Ts          BP
Hour     1.000000000 -0.006779489 -0.006775002 -0.007511024  0.01217860  0.008592576  0.02505819
DOY     -0.006779489  1.000000000  0.999999958  0.903704617 -0.68012490 -0.906918658 -0.17232397
POS_DOY -0.006775002  0.999999958  1.000000000  0.903696909 -0.68013445 -0.906916964 -0.17230481
Lat     -0.007511024  0.903704617  0.903696909  1.000000000 -0.59748962 -0.958835395 -0.29666821
Lon      0.012178596 -0.680124899 -0.680134450 -0.597489620  1.00000000  0.564136723 -0.02981279
Ts       0.008592576 -0.906918658 -0.906916964 -0.958835395  0.56413672  1.000000000  0.20223136
BP       0.025058188 -0.172323969 -0.172304805 -0.296668215 -0.02981279  0.202231363  1.00000000
# Group by DOY and average replicated measurements
data$DOY <- round(data$DOY,0)
data.avg = aggregate(list(data=data[,c('Ts','Lat')]), by=list(DOY=data$DOY), FUN=mean)
# least squares line
ggplot(data.avg,aes(x=data.avg$DOY, y=data.avg$data.Ts)) + 
  geom_point(color='#2980B9', size = 4) + 
  geom_smooth(method=lm, color='#2C3E50') +ggtitle(label ="Least Squares Regression Line") + xlab("Day Of Year") +
  ylab("Temperature")

fit1<-lm(formula = data.Ts ~ DOY, data = data.avg)
summary(fit1)

Call:
lm(formula = data.Ts ~ DOY, data = data.avg)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.95439 -0.34633  0.03139  0.35247  0.84693 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept)  9.353218   0.114242   81.87   <2e-16 ***
DOY         -0.040253   0.001989  -20.23   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.4741 on 86 degrees of freedom
Multiple R-squared:  0.8264,    Adjusted R-squared:  0.8244 
F-statistic: 409.4 on 1 and 86 DF,  p-value: < 2.2e-16
ggplot(data.avg,aes(x=data.avg$data.Lat, y=data.avg$data.Ts)) + 
  geom_point(color='#2980B9', size = 4) + 
  geom_smooth(method=lm, color='#2C3E50') +ggtitle(label ="Least Squares Regression Line")+ xlab("Lattitude") +
  ylab("Temperature")

fit2<-lm(formula = data.Ts ~ data.Lat, data = data.avg)
summary(fit2)

Call:
lm(formula = data.Ts ~ data.Lat, data = data.avg)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.51377 -0.26924 -0.04201  0.24403  0.64902 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  47.9901     1.2672   37.87   <2e-16 ***
data.Lat     -0.6202     0.0193  -32.14   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.3155 on 86 degrees of freedom
Multiple R-squared:  0.9231,    Adjusted R-squared:  0.9222 
F-statistic:  1033 on 1 and 86 DF,  p-value: < 2.2e-16
# Polynomial Regression Line
fit3<-lm(formula = data.Ts ~ DOY + data.Lat, data = data.avg)
summary(fit3)

Call:
lm(formula = data.Ts ~ DOY + data.Lat, data = data.avg)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.60721 -0.22496 -0.00537  0.25822  0.61356 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 40.049638   2.673979  14.978  < 2e-16 ***
DOY         -0.009756   0.002936  -3.322  0.00132 ** 
data.Lat    -0.491566   0.042805 -11.484  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.2985 on 85 degrees of freedom
Multiple R-squared:  0.932, Adjusted R-squared:  0.9304 
F-statistic: 582.1 on 2 and 85 DF,  p-value: < 2.2e-16
qqnorm(fit2$residuals, main=paste('Normal Q-Q Plot with', title.residuals1), cex.main=1)
qqline(fit2$residuals, col='red')

title.residuals1 <- 'Residuals of Least Square Regression Line'
plot(fit2$residuals, main=title.residuals1, ylab = "Standardized Residuals")
abline(0, 0, col='red')

LS0tCnRpdGxlOiAnQ0FTRSBTVFVEWSA0OicKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBzdHVkeSBhbGxvd3MgdXMgdG8gcmV2aXNpdC9yZW5ldwoKMS4gUmVncmVzc2lvbiBtb2RlbGluZwoyLiBQcm9wZXJ0aWVzIG9mIExlYXN0IFNxdWFyZXMvRml0dGluZyAiYSBsaW5lIgozLiBNdWx0aXBsZSBvYnNlcnZhdGlvbgoKRGF0YXNldHMgZm9yIHRoaXMgc3R1ZHkgYXJlCgoxLiBUaGUgbWFpbiBmaWxlOiBnYXVnZS50eHQKMi4gU3VwcGxlbWVudGFyeSBsYXJnZS1zY2FsZSBmaWxlczogZG93bmxvYWQgdGhlIGZvbGxvd2luZyBmb2xkZXIgRnVsbCBSZXNvbHV0aW9uIERhdGEuemlwIE1vcmUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHN1cHBsZW1lbnRhcnkgZmlsZSBjYW4gYmUgZm91bmQgYXQgaHR0cDovL2lhYnAuYXBsLndhc2hpbmd0b24uZWR1L2RhdGEuaHRtbCBhcyB3ZWxsIGFzIGh0dHA6Ly9uc2lkYy5vcmcvZGF0YS9HMDA3OTEKCgojIyBRdWVzdGlvbgpUaGUgYWltIG9mIHRoaXMgbGFiIGlzIHRvIHByb3ZpZGUgYSBzaW1wbGUgcHJvY2VkdXJlIGZvciBjb252ZXJ0aW5nIGdhaW4gaW50byBkZW5zaXR5IHdoZW4gdGhlIGdhdWdlIGlzIGluIG9wZXJhdGlvbi4gS2VlcCBpbiBtaW5kIHRoYXQgdGhlIGV4cGVyaW1lbnQgd2FzIGNvbmR1Y3RlZCBieSB2YXJ5aW5nIGRlbnNpdHkgYW5kIG1lYXN1cmluZyB0aGUgcmVzcG9uc2UgaW4gZ2FpbiwgYnV0IHdoZW4gdGhlIGdhdWdlIGlzIHVsdGltYXRlbHkgaW4gdXNlLCB0aGUgc25vdy1wYWNrIGRlbnNpdHkgaXMgdG8gYmUgZXN0aW1hdGVkIGZyb20gdGhlIG1lYXN1cmVkIGdhaW4uCgoKIyMgU2V0dXAKYGBge3J9CmRmIDwtIHJlYWQudGFibGUoJ2dhdWdlLTF3YjF3YTYtMmdwZWw0MS50eHQnLCBoZWFkZXI9VFJVRSkKZGYgPC0gZGZbb3JkZXIoZGYkZGVuc2l0eSksIF0gICMgU29ydCBmcm9tIGxlYXN0IHRvIGdyZWF0ZXN0IGRlbnNpdHkKCm0gPC0gOSAgIyBOdW1iZXIgb2YgZGlzdGluY3QgYmxvY2sgZGVuc2l0aWVzCnQgPC0gMTAgICMgTnVtYmVyIG9mIHJlcGxpY2F0ZSBtZWFzdXJlbWVudHMKCiNpbnN0YWxsLnBhY2thZ2VzKCdMMXBhY2snKQojaW5zdGFsbC5wYWNrYWdlcygncXVhbnRyZWcnKQojaW5zdGFsbC5wYWNrYWdlcygnZ2dwbG90MicpCmxpYnJhcnkoTDFwYWNrKSAgIyBVc2VkIGZvciBsZWFzdCBhYnNvbHV0ZSBkZXZpYXRpb25zIHJlZ3Jlc3Npb24gbGluZQpsaWJyYXJ5KHF1YW50cmVnKSAgIyBVc2VkIGZvciBxdWFudGlsZSByZWdyZXNzaW9uIGxpbmUKbGlicmFyeShnZ3Bsb3QyKQpgYGAKCgojIyBTY2VuYXJpbyAxOiBGaXR0aW5nClVzZSB0aGUgZGF0YSB0byBmaXQgdGhlIGdhaW4sIG9yIGEgdHJhbnNmb3JtYXRpb24gb2YgZ2FpbiwgdG8gZGVuc2l0eS4gVHJ5IHNrZXRjaGluZyB0aGUgbGVhc3Qgc3F1YXJlcyBsaW5lIG9uIGEgc2NhdHRlciBwbG90LgoKKiBEbyB0aGUgcmVzaWR1YWxzIGluZGljYXRlIGFueSBwcm9ibGVtcyB3aXRoIHRoZSBmaXQ/CiogSWYgdGhlIGRlbnNpdGllcyBvZiB0aGUgcG9seWV0aHlsZW5lIGJsb2NrcyBhcmUgbm90IHJlcG9ydGVkIGV4YWN0bHksIGhvdyBtaWdodCB0aGlzIGFmZmVjdCB0aGUgZml0PwoqIFdoYXQgaWYgdGhlIGJsb2NrcyBvZiBwb2x5ZXRoeWxlbmUgd2VyZSBub3QgbWVhc3VyZWQgaW4gcmFuZG9tIG9yZGVyIChsb2NhdGlvbik/CmBgYHtyfQojIFBsb3QgcmF3IGRhdGEKdGl0bGUgPC0gJ0RlbnNpdHkgdnMuIEdhaW4nCnguYXhpcyA8LSBleHByZXNzaW9uKCdEZW5zaXR5IChnL2NtJ14zKicpJykKeS5heGlzIDwtICdHYWluJwp4LnJhbmdlIDwtIGMoMCwgLjcpCnkucmFuZ2UgPC0gYygyLjUsIDYpCnBsb3QoZGYsIG1haW49dGl0bGUsIHhsYWI9eC5heGlzLCB5bGFiPXkuYXhpcywgeGxpbT14LnJhbmdlKQoKCiMgVGFrZSBsb2cgdHJhbnNmb3JtYXRpb24gb2YgcmVzcG9uc2UgdmFyaWFibGUgKGdhaW4pCnkubG9nLmF4aXMgPSAnbG9nKEdhaW4pJwpkZi5sb2cgPSBkYXRhLmZyYW1lKGRmWydkZW5zaXR5J10sIGxvZyhkZlsnZ2FpbiddKSkKcGxvdChkZi5sb2csIG1haW49dGl0bGUsIHhsYWI9eC5heGlzLCB5bGFiPXkubG9nLmF4aXMsIHhsaW09eC5yYW5nZSwgeWxpbT15LnJhbmdlKQoKCiMgQXZlcmFnZSByZXBsaWNhdGUgbWVhc3VyZW1lbnRzCmRmLmxvZy5hdmcgPSBhZ2dyZWdhdGUobGlzdChnYWluPWRmLmxvZyRnYWluKSwgYnk9bGlzdChkZW5zaXR5PWRmLmxvZyRkZW5zaXR5KSwgRlVOPW1lYW4pCnBsb3QoZGYubG9nLmF2ZywgbWFpbj10aXRsZSwgeGxhYj14LmF4aXMsIHlsYWI9eS5sb2cuYXhpcywgeGxpbT14LnJhbmdlLCB5bGltPXkucmFuZ2UpCgoKIyBGaXQgZ2FpbiB0byBkZW5zaXR5CmxlYXN0LnNxdWFyZXMgPC0gbG0oZ2Fpbn5kZW5zaXR5LCBkYXRhPWRmLmxvZy5hdmcpCmxhZCA8LSBsYWQoZ2Fpbn5kZW5zaXR5LCBkYXRhPWRmLmxvZy5hdmcpCnF1YW50IDwtIHJxKGdhaW5+ZGVuc2l0eSwgdGF1PS41LCBkYXRhPWRmLmxvZy5hdmcpCgpwbG90KGRmLmxvZywgbWFpbj10aXRsZSwgeGxhYj14LmF4aXMsIHlsYWI9eS5sb2cuYXhpcywgeGxpbT14LnJhbmdlLCB5bGltPXkucmFuZ2UpCmFibGluZShsZWFzdC5zcXVhcmVzLCBjb2w9J3JlZCcpCmFibGluZShsYWQsIGNvbD0nYmx1ZScpCmFibGluZShxdWFudCwgY29sPSdncmVlbicpCmxlZ2VuZCgndG9wcmlnaHQnLCBsZWdlbmQ9YygnTGVhc3QgU3F1YXJlcyBSZWdyZXNzaW9uIExpbmUnLCAnTGVhc3QgQWJzb2x1dGUgRGV2aWF0aW9ucyBSZWdyZXNzaW9uIExpbmUnLCAnNTAlIFF1YW50aWxlIFJlZ3Jlc3Npb24gTGluZScpLCBjb2w9YygncmVkJywgJ2JsdWUnLCAnZ3JlZW4nKSwgbHR5PTEpCgpjKGNvcihkZi5sb2cuYXZnKSwgc3VtbWFyeShsZWFzdC5zcXVhcmVzKSRyLnNxdWFyZWQpCmxlYXN0LnNxdWFyZXMKbGFkCnF1YW50CgoKIyBDaGVjayBjb25kaXRpb25zIGZvciBsaW5lYXIgcmVncmVzc2lvbjogbGluZWFyaXR5LCBub3JtYWxpdHkgb2YgcmVzaWR1YWxzLCBhbmQgY29uc3RhbnQgdmFyaWFiaWxpdHkKbGVhc3Quc3F1YXJlcy5yZXNpZHVhbHMgPC0gZGF0YS5mcmFtZShkZi5sb2dbJ2RlbnNpdHknXSwgZGYubG9nWydnYWluJ10gLSByZXAocHJlZGljdChsZWFzdC5zcXVhcmVzKSwgZWFjaD0xMCkpCmxhZC5yZXNpZHVhbHMgPC0gZGF0YS5mcmFtZShkZi5sb2dbJ2RlbnNpdHknXSwgZGYubG9nWydnYWluJ10gLSByZXAocHJlZGljdChsYWQpLCBlYWNoPTEwKSkKcXVhbnQucmVzaWR1YWxzIDwtIGRhdGEuZnJhbWUoZGYubG9nWydkZW5zaXR5J10sIGRmLmxvZ1snZ2FpbiddIC0gcmVwKHByZWRpY3QocXVhbnQpLCBlYWNoPTEwKSkKCnRpdGxlLnJlc2lkdWFsczEgPC0gJ1Jlc2lkdWFscyBvZiBMZWFzdCBTcXVhcmVzIFJlZ3Jlc3Npb24gTGluZScKdGl0bGUucmVzaWR1YWxzMiA8LSAnUmVzaWR1YWxzIG9mIExlYXN0IEFic29sdXRlIERldmlhdGlvbnMgUmVncmVzc2lvbiBMaW5lJwp0aXRsZS5yZXNpZHVhbHMzIDwtICdSZXNpZHVhbHMgb2YgNTAlIFF1YW50aWxlIFJlZ3Jlc3Npb24gTGluZScKcGxvdChsZWFzdC5zcXVhcmVzLnJlc2lkdWFscyRnYWluLCBtYWluPXRpdGxlLnJlc2lkdWFsczEsIHlsYWI9eS5heGlzKQphYmxpbmUoMCwgMCwgY29sPSdyZWQnKQpwbG90KGxhZC5yZXNpZHVhbHMkZ2FpbiwgbWFpbj10aXRsZS5yZXNpZHVhbHMyLCB5bGFiPXkuYXhpcykKYWJsaW5lKDAsIDAsIGNvbD0nYmx1ZScpCnBsb3QocXVhbnQucmVzaWR1YWxzJGdhaW4sIG1haW49dGl0bGUucmVzaWR1YWxzMywgeWxhYj15LmF4aXMpCmFibGluZSgwLCAwLCBjb2w9J2dyZWVuJykKCm51bS5iaW5zIDwtIDEyCmhpc3QobGVhc3Quc3F1YXJlcy5yZXNpZHVhbHMkZ2FpbiwgYnJlYWtzPW51bS5iaW5zLCBtYWluPXRpdGxlLnJlc2lkdWFsczEsIHhsYWI9eS5heGlzLCBjb2w9J3JlZCcpCmhpc3QobGFkLnJlc2lkdWFscyRnYWluLCBicmVha3M9bnVtLmJpbnMsIG1haW49dGl0bGUucmVzaWR1YWxzMiwgeGxhYj15LmF4aXMsIGNvbD0nYmx1ZScpCmhpc3QocXVhbnQucmVzaWR1YWxzJGdhaW4sIGJyZWFrcz1udW0uYmlucywgbWFpbj10aXRsZS5yZXNpZHVhbHMzLCB4bGFiPXkuYXhpcywgY29sPSdncmVlbicpCgpxcW5vcm0obGVhc3Quc3F1YXJlcy5yZXNpZHVhbHMkZ2FpbiwgbWFpbj1wYXN0ZSgnTm9ybWFsIFEtUSBQbG90IHdpdGgnLCB0aXRsZS5yZXNpZHVhbHMxKSwgY2V4Lm1haW49MSkKcXFsaW5lKGxlYXN0LnNxdWFyZXMucmVzaWR1YWxzJGdhaW4sIGNvbD0ncmVkJykKcXFub3JtKGxhZC5yZXNpZHVhbHMkZ2FpbiwgbWFpbj1wYXN0ZSgnTm9ybWFsIFEtUSBQbG90IHdpdGgnLCB0aXRsZS5yZXNpZHVhbHMyKSwgY2V4Lm1haW49MSkKcXFsaW5lKGxhZC5yZXNpZHVhbHMkZ2FpbiwgY29sPSdibHVlJykKcXFub3JtKHF1YW50LnJlc2lkdWFscyRnYWluLCBtYWluPXBhc3RlKCdOb3JtYWwgUS1RIFBsb3Qgd2l0aCcsIHRpdGxlLnJlc2lkdWFsczMpLCBjZXgubWFpbj0xKQpxcWxpbmUocXVhbnQucmVzaWR1YWxzJGdhaW4sIGNvbD0nZ3JlZW4nKQpgYGAKCgojIyBTY2VuYXJpbyAyOiBQcmVkaWN0aW5nClVsdGltYXRlbHkgd2UgYXJlIGludGVyZXN0ZWQgaW4gYW5zd2VyaW5nIHF1ZXN0aW9ucyBzdWNoIGFzOiBHaXZlbiBhIGdhaW4gcmVhZGluZyBvZiAzOC42LCB3aGF0IGlzIHRoZSBkZW5zaXR5IG9mIHRoZSBzbm93LXBhY2s/IEdpdmVuIGEgZ2FpbiByZWFkaW5nIG9mIDQyNi43LCB3aGF0IGlzIHRoZSBkZW5zaXR5IG9mIHRoZSBzbm93LXBhY2s/IFRoZXNlIHR3byBudW1lcmljIHZhbHVlcywgMzguNiBhbmQgNDI2LjcsIHdlcmUgY2hvc2VuIGJlY2F1c2UgdGhleSBhcmUgdGhlIGF2ZXJhZ2UgZ2FpbnMgZm9yIHRoZSAwLjUwOCBhbmQgMC4wMDEgZGVuc2l0aWVzLCByZXNwZWN0aXZlbHkuCgoqIERldmVsb3AgYSBwcm9jZWR1cmUgZm9yIGFkZGluZyBiYW5kcyBhcm91bmQgeW91ciBsZWFzdCBzcXVhcmVzIGxpbmUgdGhhdCBjYW4gYmUgdXNlZCB0byBtYWtlIGludGVydmFsIGVzdGltYXRlcyBmb3IgdGhlIHNub3ctcGFjayBkZW5zaXR5IGZyb20gZ2FpbiBtZWFzdXJlbWVudHMuIEtlZXAgaW4gbWluZCBob3cgdGhlIGRhdGEgd2VyZSBjb2xsZWN0ZWQ6IHNldmVyYWwgbWVhc3VyZW1lbnRzIG9mIGdhaW4gd2VyZSB0YWtlbiBmb3IgcG9seWVueXRoeWxlbmUgYmxvY2tzIG9mIGtub3duIGRlbnNpdHkuCmBgYHtyIGZpZy5hc3A9MiwgZmlnLndpZHRoPTV9CiMgUHJlZGljdGlvbnMKUHJlZGljdExvZ0dhaW4gPC0gZnVuY3Rpb24oZGVuc2l0eSkKICBwcmVkaWN0KGxlYXN0LnNxdWFyZXMsIGRhdGEuZnJhbWUoZGVuc2l0eT1kZW5zaXR5KSkgICMgUHJlZGljdCBsb2coZ2FpbikgdXNpbmcgZGVuc2l0eQoKUHJlZGljdERlbnNpdHlMZWFzdFNxdWFyZXMgPC0gZnVuY3Rpb24oZ2FpbikgewogIGludGVyY2VwdCA8LSBjb2VmKGxlYXN0LnNxdWFyZXMpW1sxXV0KICBzbG9wZSA8LSBjb2VmKGxlYXN0LnNxdWFyZXMpW1syXV0KICAobG9nKGdhaW4pIC0gaW50ZXJjZXB0KSAvIHNsb3BlICAjIFByZWRpY3QgZGVuc2l0eSB1c2luZyBnYWluCn0KClByZWRpY3REZW5zaXR5TGFkIDwtIGZ1bmN0aW9uKGdhaW4pIHsKICBpbnRlcmNlcHQgPC0gY29lZihsYWQpW1sxXV0KICBzbG9wZSA8LSBjb2VmKGxhZClbWzJdXQogIChsb2coZ2FpbikgLSBpbnRlcmNlcHQpIC8gc2xvcGUgICMgUHJlZGljdCBkZW5zaXR5IHVzaW5nIGdhaW4KfQoKUHJlZGljdERlbnNpdHlRdWFudCA8LSBmdW5jdGlvbihnYWluKSB7CiAgaW50ZXJjZXB0IDwtIGNvZWYocXVhbnQpW1sxXV0KICBzbG9wZSA8LSBjb2VmKHF1YW50KVtbMl1dCiAgKGxvZyhnYWluKSAtIGludGVyY2VwdCkgLyBzbG9wZSAgIyBQcmVkaWN0IGRlbnNpdHkgdXNpbmcgZ2Fpbgp9CgoKIyA5NSUgcHJlZGljdGlvbiBhbmQgY29uZmlkZW5jZSBpbnRlcnZhbHMgb2YgbG9nKGdhaW4pIHVzaW5nIGRlbnNpdHkKdCA8LSBxdCguOTc1LCBkZj1tLTIpCm1lYW4uZGVuc2l0eSA8LSBtZWFuKGRmLmxvZy5hdmckZGVuc2l0eSkKc3VtbWF0aW9uIDwtIHN1bSgoZGYubG9nLmF2ZyRkZW5zaXR5IC0gbWVhbi5kZW5zaXR5KSBeIDIpCgpzMiA8LSBhZ2dyZWdhdGUobGlzdCh2YXJpYW5jZT1sZWFzdC5zcXVhcmVzLnJlc2lkdWFscyRnYWluKSwgYnk9bGlzdChkZW5zaXR5PWxlYXN0LnNxdWFyZXMucmVzaWR1YWxzJGRlbnNpdHkpLCBGVU49dmFyKQpzLnBvb2xlZCA8LSBzcXJ0KG1lYW4oczIkdmFyaWFuY2UpKQoKY2VudGVyLmV4cHIgPC0gcXVvdGUoY2VudGVyIDwtIFByZWRpY3RMb2dHYWluKGRlbnNpdHkpKQpjaS53aWR0aC5leHByIDwtIHF1b3RlKHdpZHRoIDwtIHQgKiBzLnBvb2xlZCAqIHNxcnQoMS9tICsgKGRlbnNpdHktbWVhbi5kZW5zaXR5KV4yIC8gc3VtbWF0aW9uKSkKcGkud2lkdGguZXhwciA8LSBxdW90ZSh3aWR0aCA8LSB0ICogcy5wb29sZWQgKiBzcXJ0KDEgKyAxL20gKyAoZGVuc2l0eS1tZWFuLmRlbnNpdHkpXjIgLyBzdW1tYXRpb24pKQoKTG9nR2FpbkNpTG93ZXIgPC0gZnVuY3Rpb24oZGVuc2l0eSkgewogIGV2YWwoY2VudGVyLmV4cHIpCiAgZXZhbChjaS53aWR0aC5leHByKQogIGNlbnRlciAtIHdpZHRoCn0KCkxvZ0dhaW5DaVVwcGVyIDwtIGZ1bmN0aW9uKGRlbnNpdHkpIHsKICBldmFsKGNlbnRlci5leHByKQogIGV2YWwoY2kud2lkdGguZXhwcikKICBjZW50ZXIgKyB3aWR0aAp9CgpMb2dHYWluUGlMb3dlciA8LSBmdW5jdGlvbihkZW5zaXR5KSB7CiAgZXZhbChjZW50ZXIuZXhwcikKICBldmFsKHBpLndpZHRoLmV4cHIpCiAgY2VudGVyIC0gd2lkdGgKfQoKTG9nR2FpblBpVXBwZXIgPC0gZnVuY3Rpb24oZGVuc2l0eSkgewogIGV2YWwoY2VudGVyLmV4cHIpCiAgZXZhbChwaS53aWR0aC5leHByKQogIGNlbnRlciArIHdpZHRoCn0KCiMgQWRkIGJhbmRzIGFyb3VuZCBsZWFzdCBzcXVhcmVzIGxpbmUKcGxvdChkZi5sb2csIG1haW49dGl0bGUsIHhsYWI9eC5heGlzLCB5bGFiPXkubG9nLmF4aXMsIHhsaW09eC5yYW5nZSwgeWxpbT15LnJhbmdlKQphYmxpbmUobGVhc3Quc3F1YXJlcywgY29sPSdyZWQnKQoKY2kuY29sIDwtICdwdXJwbGUnCnBpLmNvbCA8LSAnYmx1ZScKc3ltYm9sIDwtICctJwpzaXplIDwtIDEuNQpsaW5lLnR5cGUgPC0gMwpsaW5lLndpZHRoIDwtIDAuNwoKY29uZmlkZW5jZS5pbnRlcnZhbHMgPC0gZGF0YS5mcmFtZShkZW5zaXR5PWRmLmxvZy5hdmckZGVuc2l0eSwgbG93ZXI9TG9nR2FpbkNpTG93ZXIoZGYubG9nLmF2ZyRkZW5zaXR5KSwgdXBwZXI9TG9nR2FpbkNpVXBwZXIoZGYubG9nLmF2ZyRkZW5zaXR5KSkKcG9pbnRzKHg9Y29uZmlkZW5jZS5pbnRlcnZhbHMkZGVuc2l0eSwgeT1jb25maWRlbmNlLmludGVydmFscyRsb3dlciwgY29sPWNpLmNvbCwgcGNoPXN5bWJvbCwgY2V4PXNpemUpCnBvaW50cyh4PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGRlbnNpdHksIHk9Y29uZmlkZW5jZS5pbnRlcnZhbHMkdXBwZXIsIGNvbD1jaS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQpsaW5lcyh4PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGRlbnNpdHksIHk9Y29uZmlkZW5jZS5pbnRlcnZhbHMkbG93ZXIsIGNvbD1jaS5jb2wsIGx0eT1saW5lLnR5cGUsIGx3ZD1saW5lLndpZHRoKQpsaW5lcyh4PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGRlbnNpdHksIHk9Y29uZmlkZW5jZS5pbnRlcnZhbHMkdXBwZXIsIGNvbD1jaS5jb2wsIGx0eT1saW5lLnR5cGUsIGx3ZD1saW5lLndpZHRoKQoKcHJlZGljdGlvbi5pbnRlcnZhbHMgPC0gZGF0YS5mcmFtZShkZW5zaXR5PWRmLmxvZy5hdmckZGVuc2l0eSwgbG93ZXI9TG9nR2FpblBpTG93ZXIoZGYubG9nLmF2ZyRkZW5zaXR5KSwgdXBwZXI9TG9nR2FpblBpVXBwZXIoZGYubG9nLmF2ZyRkZW5zaXR5KSkKcG9pbnRzKHg9cHJlZGljdGlvbi5pbnRlcnZhbHMkZGVuc2l0eSwgeT1wcmVkaWN0aW9uLmludGVydmFscyRsb3dlciwgY29sPXBpLmNvbCwgcGNoPXN5bWJvbCwgY2V4PXNpemUpCnBvaW50cyh4PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGRlbnNpdHksIHk9cHJlZGljdGlvbi5pbnRlcnZhbHMkdXBwZXIsIGNvbD1waS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQpsaW5lcyh4PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGRlbnNpdHksIHk9cHJlZGljdGlvbi5pbnRlcnZhbHMkbG93ZXIsIGNvbD1waS5jb2wsIGx0eT1saW5lLnR5cGUsIGx3ZD1saW5lLndpZHRoKQpsaW5lcyh4PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGRlbnNpdHksIHk9cHJlZGljdGlvbi5pbnRlcnZhbHMkdXBwZXIsIGNvbD1waS5jb2wsIGx0eT1saW5lLnR5cGUsIGx3ZD1saW5lLndpZHRoKQoKbGVnZW5kKCd0b3ByaWdodCcsIGxlZ2VuZD1jKCdMZWFzdCBTcXVhcmVzIFJlZ3Jlc3Npb24gTGluZScsICc5NSUgQ29uZmlkZW5jZSBJbnRlcnZhbCBCYW5kcycsICc5NSUgUHJlZGljdGlvbiBJbnRlcnZhbCBCYW5kcycpLCBjb2w9YygncmVkJywgY2kuY29sLCBwaS5jb2wpLCBsdHk9MSkKCgojIDk1JSBwcmVkaWN0aW9uIGFuZCBjb25maWRlbmNlIGludGVydmFscyBvZiBkZW5zaXR5IHVzaW5nIGdhaW4KZW5kLnBvaW50cyA8LSBjKC0xLCAzKSAgIyBJbnRlcnZhbCB0byBzZWFyY2ggdGhlIHJvb3QgaW4KCkRlbnNpdHlDaSA8LSBmdW5jdGlvbihnYWluKSB7CiAgbG93ZXIgPC0gdW5pcm9vdChmdW5jdGlvbihkZW5zaXR5KSBsb2coZ2FpbikgLSBMb2dHYWluQ2lMb3dlcihkZW5zaXR5KSwgaW50ZXJ2YWw9ZW5kLnBvaW50cylbWzFdXQogIHVwcGVyIDwtIHVuaXJvb3QoZnVuY3Rpb24oZGVuc2l0eSkgbG9nKGdhaW4pIC0gTG9nR2FpbkNpVXBwZXIoZGVuc2l0eSksIGludGVydmFsPWVuZC5wb2ludHMpW1sxXV0KICBjKGxvd2VyLCB1cHBlcikKfQoKRGVuc2l0eVBpIDwtIGZ1bmN0aW9uKGdhaW4pIHsKICBsb3dlciA8LSB1bmlyb290KGZ1bmN0aW9uKGRlbnNpdHkpIGxvZyhnYWluKSAtIExvZ0dhaW5QaUxvd2VyKGRlbnNpdHkpLCBlbmQucG9pbnRzKVtbMV1dCiAgdXBwZXIgPC0gdW5pcm9vdChmdW5jdGlvbihkZW5zaXR5KSBsb2coZ2FpbikgLSBMb2dHYWluUGlVcHBlcihkZW5zaXR5KSwgZW5kLnBvaW50cylbWzFdXQogIGMobG93ZXIsIHVwcGVyKQp9CgoKIyBQb2ludCBhbmQgaW50ZXJ2YWwgZXN0aW1hdGVzIGZvciBleGFtcGxlIGdhaW4gcmVhZGluZ3MKUHJlZGljdERlbnNpdHlMZWFzdFNxdWFyZXMoMzguNikgICMgMzguNiBpcyB0aGUgYXZlcmFnZSBnYWluIGZvciAwLjUwOCBkZW5zaXR5ClByZWRpY3REZW5zaXR5TGFkKDM4LjYpClByZWRpY3REZW5zaXR5UXVhbnQoMzguNikKRGVuc2l0eUNpKDM4LjYpCkRlbnNpdHlQaSgzOC42KQoKUHJlZGljdERlbnNpdHlMZWFzdFNxdWFyZXMoNDI2LjcpICAjIDQyNi43IGlzIHRoZSBhdmVyYWdlIGdhaW4gZm9yIDAuMDAxIGRlbnNpdHkKUHJlZGljdERlbnNpdHlMYWQoNDI2LjcpClByZWRpY3REZW5zaXR5UXVhbnQoNDI2LjcpCkRlbnNpdHlDaSg0MjYuNykKRGVuc2l0eVBpKDQyNi43KQpgYGAKCgojIyBTY2VuYXJpbyAzOiBDcm9zcy1WYWxpZGF0aW9uClRvIGNoZWNrIGhvdyB3ZWxsIHlvdXIgcHJvY2VkdXJlIHdvcmtzLCBvbWl0IHRoZSBzZXQgb2YgbWVhc3VyZW1lbnRzIGNvcnJlc3BvbmRpbmcgdG8gdGhlIGJsb2NrIG9mIGRlbnNpdHkgMC41MDgsIGFwcGx5IHlvdXIgImVzdGltYXRpb24iL2NhbGlicmF0aW9uIHByb2NlZHVyZSB0byB0aGUgcmVtYWluaW5nIGRhdGEsIGFuZCBwcm92aWRlIGFuIGludGVydmFsIGVzdGltYXRlIGZvciB0aGUgZGVuc2l0eSBvZiBhIGJsb2NrIHdpdGggYW4gYXZlcmFnZSByZWFkaW5nIG9mIDM4LjYuIFdoZXJlIGRvZXMgdGhlIGFjdHVhbCBkZW5zaXR5IGZhbGwgaW4gdGhlIGludGVydmFsPyBUcnkgdGhlIHNhbWUgdGVzdCwgZm9yIHRoZSBzZXQgb2YgbWVhc3VyZW1lbnRzIGF0IHRoZSAwLjAwMSBkZW5zaXR5LgpgYGB7ciBmaWcuYXNwPTIsIGZpZy53aWR0aD01fQpmb3IgKG9taXR0ZWQgaW4gYygwLjUwOCwgMC4wMDEpKSB7CiAgIyBPbWl0IG1lYXN1cmVtZW50cyBjb3JyZXNwb25kaW5nIHRvIHRoZSBzcGVjaWZpZWQgZGVuc2l0eQogIGRmLmxvZy5vbWl0dGVkID0gZGYubG9nW3doaWNoKGRmLmxvZ1snZGVuc2l0eSddICE9IG9taXR0ZWQpLCBdCiAgZGYubG9nLmF2Zy5vbWl0dGVkIDwtIGRmLmxvZy5hdmdbd2hpY2goZGYubG9nLmF2Z1snZGVuc2l0eSddICE9IG9taXR0ZWQpLCBdCiAgCiAgCiAgIyBSZWRvIGNhbGN1bGF0aW9ucyB1c2luZyBtb2RpZmllZCBkYXRhc2V0CiAgbGVhc3Quc3F1YXJlcyA8LSBsbShnYWlufmRlbnNpdHksIGRhdGE9ZGYubG9nLmF2Zy5vbWl0dGVkKQogIAogIG1lYW4uZGVuc2l0eSA8LSBtZWFuKGRmLmxvZy5hdmcub21pdHRlZCRkZW5zaXR5KQogIHN1bW1hdGlvbiA8LSBzdW0oKGRmLmxvZy5hdmcub21pdHRlZCRkZW5zaXR5IC0gbWVhbi5kZW5zaXR5KSBeIDIpCiAgCiAgczIgPC0gYWdncmVnYXRlKGxpc3QodmFyaWFuY2U9bGVhc3Quc3F1YXJlcy5yZXNpZHVhbHMkZ2FpbiksIGJ5PWxpc3QoZGVuc2l0eT1sZWFzdC5zcXVhcmVzLnJlc2lkdWFscyRkZW5zaXR5KSwgRlVOPXZhcikKICBzLnBvb2xlZCA8LSBzcXJ0KG1lYW4oczIkdmFyaWFuY2UpKQogIAogIGNpLndpZHRoLmV4cHIgPC0gcXVvdGUod2lkdGggPC0gdCAqIHMucG9vbGVkICogc3FydCgxLyhtLTEpICsgKGRlbnNpdHktbWVhbi5kZW5zaXR5KV4yIC8gc3VtbWF0aW9uKSkKICBwaS53aWR0aC5leHByIDwtIHF1b3RlKHdpZHRoIDwtIHQgKiBzLnBvb2xlZCAqIHNxcnQoMSArIDEvKG0tMSkgKyAoZGVuc2l0eS1tZWFuLmRlbnNpdHkpXjIgLyBzdW1tYXRpb24pKQogIAogIHBsb3QoZGYubG9nLm9taXR0ZWQsIG1haW49dGl0bGUsIHhsYWI9eC5heGlzLCB5bGFiPXkubG9nLmF4aXMsIHhsaW09eC5yYW5nZSwgeWxpbT15LnJhbmdlKQogIGFibGluZShsZWFzdC5zcXVhcmVzLCBjb2w9J3JlZCcpCiAgCiAgY2kuY29sIDwtICdwdXJwbGUnCiAgcGkuY29sIDwtICdibHVlJwogIHN5bWJvbCA8LSAnLScKICBzaXplIDwtIDEuNQogIGxpbmUudHlwZSA8LSAzCiAgbGluZS53aWR0aCA8LSAwLjcKICAKICBjb25maWRlbmNlLmludGVydmFscyA8LSBkYXRhLmZyYW1lKGRlbnNpdHk9ZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHksIGxvd2VyPUxvZ0dhaW5DaUxvd2VyKGRmLmxvZy5hdmcub21pdHRlZCRkZW5zaXR5KSwgdXBwZXI9TG9nR2FpbkNpVXBwZXIoZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHkpKQogIHBvaW50cyh4PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGRlbnNpdHksIHk9Y29uZmlkZW5jZS5pbnRlcnZhbHMkbG93ZXIsIGNvbD1jaS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQogIHBvaW50cyh4PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGRlbnNpdHksIHk9Y29uZmlkZW5jZS5pbnRlcnZhbHMkdXBwZXIsIGNvbD1jaS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQogIGxpbmVzKHg9Y29uZmlkZW5jZS5pbnRlcnZhbHMkZGVuc2l0eSwgeT1jb25maWRlbmNlLmludGVydmFscyRsb3dlciwgY29sPWNpLmNvbCwgbHR5PWxpbmUudHlwZSwgbHdkPWxpbmUud2lkdGgpCiAgbGluZXMoeD1jb25maWRlbmNlLmludGVydmFscyRkZW5zaXR5LCB5PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJHVwcGVyLCBjb2w9Y2kuY29sLCBsdHk9bGluZS50eXBlLCBsd2Q9bGluZS53aWR0aCkKICAKICBwcmVkaWN0aW9uLmludGVydmFscyA8LSBkYXRhLmZyYW1lKGRlbnNpdHk9ZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHksIGxvd2VyPUxvZ0dhaW5QaUxvd2VyKGRmLmxvZy5hdmcub21pdHRlZCRkZW5zaXR5KSwgdXBwZXI9TG9nR2FpblBpVXBwZXIoZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHkpKQogIHBvaW50cyh4PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGRlbnNpdHksIHk9cHJlZGljdGlvbi5pbnRlcnZhbHMkbG93ZXIsIGNvbD1waS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQogIHBvaW50cyh4PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGRlbnNpdHksIHk9cHJlZGljdGlvbi5pbnRlcnZhbHMkdXBwZXIsIGNvbD1waS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQogIGxpbmVzKHg9cHJlZGljdGlvbi5pbnRlcnZhbHMkZGVuc2l0eSwgeT1wcmVkaWN0aW9uLmludGVydmFscyRsb3dlciwgY29sPXBpLmNvbCwgbHR5PWxpbmUudHlwZSwgbHdkPWxpbmUud2lkdGgpCiAgbGluZXMoeD1wcmVkaWN0aW9uLmludGVydmFscyRkZW5zaXR5LCB5PXByZWRpY3Rpb24uaW50ZXJ2YWxzJHVwcGVyLCBjb2w9cGkuY29sLCBsdHk9bGluZS50eXBlLCBsd2Q9bGluZS53aWR0aCkKICAKICBsZWdlbmQoJ3RvcHJpZ2h0JywgbGVnZW5kPWMoJ0xlYXN0IFNxdWFyZXMgUmVncmVzc2lvbiBMaW5lJywgJzk1JSBDb25maWRlbmNlIEludGVydmFsIEJhbmRzJywgJzk1JSBQcmVkaWN0aW9uIEludGVydmFsIEJhbmRzJyksIGNvbD1jKCdyZWQnLCBjaS5jb2wsIHBpLmNvbCksIGx0eT0xKQogIAogIHByaW50KFByZWRpY3REZW5zaXR5TGVhc3RTcXVhcmVzKDM4LjYpKSAgIyAzOC42IGlzIHRoZSBhdmVyYWdlIGdhaW4gZm9yIDAuNTA4IGRlbnNpdHkKICBwcmludChEZW5zaXR5Q2koMzguNikpCiAgcHJpbnQoRGVuc2l0eVBpKDM4LjYpKQogIAogIHByaW50KFByZWRpY3REZW5zaXR5TGVhc3RTcXVhcmVzKDQyNi43KSkgICMgNDI2LjcgaXMgdGhlIGF2ZXJhZ2UgZ2FpbiBmb3IgMC4wMDEgZGVuc2l0eQogIHByaW50KERlbnNpdHlDaSg0MjYuNykpCiAgcHJpbnQoRGVuc2l0eVBpKDQyNi43KSkKfQpgYGAKCgojIyBBZGRpdGlvbmFsIFNjZW5hcmlvOiBUZW1wZXJhdHVyZSwgRE9ZLCBhbmQgTGF0aXR1ZGUuClVzZSB0aGUgYWRkaXRpb25hbCBkYXRhc2V0IHRvIGNvbnN0cnVjdCBhIG1vZGVsIGZpdHRpbmcgdGVtcGVyYXR1cmUgd2l0aCBET1ksIGxhdGl0dWRlLCBhbmQgb3RoZXIgcmVhc29uYWJsZSBmZWF0dXJlcy4gVHJ5IHNrZXRjaGluZyB0aGUgbGVhc3Qgc3F1YXJlcyBsaW5lIG9uIGEgc2NhdHRlciBwbG90LiBXZSBhaW0gdG8gaW52ZXN0aWdhdGUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRlbXBlcmF0dXJlIGFuZCB0aGUgRE9ZLCBhbmQgaXRzIGxhdGl0dWRlLgpgYGB7cn0KIyBDaGVjayB0aGUgY29ycmVsYXRpb24KZGF0YSA8LSByZWFkLmNzdignRnVsbCBSZXNvbHV0aW9uIERhdGEvNjQ1MDY0MjAuY3N2JywgaGVhZGVyPVRSVUUpCmRhdGEgPC0gZGF0YVssYygnSG91cicsJ0RPWScsJ1BPU19ET1knLCdMYXQnLCdMb24nLCdUcycsJ0JQJyldCgojIERyb3AgdGhlIGV4dHJlbWUgb3V0bGllciBjYXNlCiNkYXRhIDwtIGRhdGFbd2hpY2goZGF0YSRUcz4tMjAwKSxdCmRhdGFfbWF0cml4IDwtIGFzLm1hdHJpeChkYXRhKQoKIyBDb3JyZWxhdGlvbiBNYXRyaXgKY29ycl9tYXRyaXggPC0gY29yKGRhdGFfbWF0cml4KQpjb3JyX21hdHJpeApgYGAKCmBgYHtyfQojIEdyb3VwIGJ5IERPWSBhbmQgYXZlcmFnZSByZXBsaWNhdGVkIG1lYXN1cmVtZW50cwpkYXRhJERPWSA8LSByb3VuZChkYXRhJERPWSwwKQpkYXRhLmF2ZyA9IGFnZ3JlZ2F0ZShsaXN0KGRhdGE9ZGF0YVssYygnVHMnLCdMYXQnKV0pLCBieT1saXN0KERPWT1kYXRhJERPWSksIEZVTj1tZWFuKQoKIyBsZWFzdCBzcXVhcmVzIGxpbmUKZ2dwbG90KGRhdGEuYXZnLGFlcyh4PWRhdGEuYXZnJERPWSwgeT1kYXRhLmF2ZyRkYXRhLlRzKSkgKyAKICBnZW9tX3BvaW50KGNvbG9yPScjMjk4MEI5Jywgc2l6ZSA9IDQpICsgCiAgZ2VvbV9zbW9vdGgobWV0aG9kPWxtLCBjb2xvcj0nIzJDM0U1MCcpICtnZ3RpdGxlKGxhYmVsID0iTGVhc3QgU3F1YXJlcyBSZWdyZXNzaW9uIExpbmUiKSArIHhsYWIoIkRheSBPZiBZZWFyIikgKwogIHlsYWIoIlRlbXBlcmF0dXJlIikKZml0MTwtbG0oZm9ybXVsYSA9IGRhdGEuVHMgfiBET1ksIGRhdGEgPSBkYXRhLmF2ZykKc3VtbWFyeShmaXQxKQoKZ2dwbG90KGRhdGEuYXZnLGFlcyh4PWRhdGEuYXZnJGRhdGEuTGF0LCB5PWRhdGEuYXZnJGRhdGEuVHMpKSArIAogIGdlb21fcG9pbnQoY29sb3I9JyMyOTgwQjknLCBzaXplID0gNCkgKyAKICBnZW9tX3Ntb290aChtZXRob2Q9bG0sIGNvbG9yPScjMkMzRTUwJykgK2dndGl0bGUobGFiZWwgPSJMZWFzdCBTcXVhcmVzIFJlZ3Jlc3Npb24gTGluZSIpKyB4bGFiKCJMYXR0aXR1ZGUiKSArCiAgeWxhYigiVGVtcGVyYXR1cmUiKQpmaXQyPC1sbShmb3JtdWxhID0gZGF0YS5UcyB+IGRhdGEuTGF0LCBkYXRhID0gZGF0YS5hdmcpCnN1bW1hcnkoZml0MikKCiMgUG9seW5vbWlhbCBSZWdyZXNzaW9uIExpbmUKZml0MzwtbG0oZm9ybXVsYSA9IGRhdGEuVHMgfiBET1kgKyBkYXRhLkxhdCwgZGF0YSA9IGRhdGEuYXZnKQpzdW1tYXJ5KGZpdDMpCgpxcW5vcm0oZml0MiRyZXNpZHVhbHMsIG1haW49cGFzdGUoJ05vcm1hbCBRLVEgUGxvdCB3aXRoJywgdGl0bGUucmVzaWR1YWxzMSksIGNleC5tYWluPTEpCnFxbGluZShmaXQyJHJlc2lkdWFscywgY29sPSdyZWQnKQoKdGl0bGUucmVzaWR1YWxzMSA8LSAnUmVzaWR1YWxzIG9mIExlYXN0IFNxdWFyZSBSZWdyZXNzaW9uIExpbmUnCnBsb3QoZml0MiRyZXNpZHVhbHMsIG1haW49dGl0bGUucmVzaWR1YWxzMSwgeWxhYiA9ICJTdGFuZGFyZGl6ZWQgUmVzaWR1YWxzIikKYWJsaW5lKDAsIDAsIGNvbD0ncmVkJykKYGBg